#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/
#endregion

using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using newtelligence.DasBlog.Runtime.Proxies;
using System.ServiceModel;
using dasBlog.Services.Contracts.WeblogUpdates;

namespace newtelligence.DasBlog.Runtime
{
	/// <summary>
	/// 
	/// </summary>
    public static class BlogDataServiceFactory
    {
		
		private static Hashtable services = new Hashtable();
        private static IBlogDataService defaultService = null;

        public static IBlogDataService GetService()
        {
            return GetService(null, null, null);
        }
        
		/// <summary>
		/// 
		/// </summary>
		/// <param name="contentLocation"></param>
		/// <returns></returns>
        public static IBlogDataService GetService(string contentLocation, string binariesLocation, ILoggingDataService loggingService)
        {
			IBlogDataService service;

            if (defaultService != null)
                return defaultService;

			lock( services.SyncRoot ) {
				service = services[contentLocation.ToUpper()] as IBlogDataService;
                if (service == null)
                {
					service = new BlogDataServiceXml(contentLocation, binariesLocation, loggingService);
					services.Add( contentLocation.ToUpper(), service );
				}
			}
			return service;
		}

        public static bool RemoveService(string contentLocation)
        {
            lock (services.SyncRoot)
            {
                if (services.ContainsKey(contentLocation.ToUpper()))
                {
					services.Remove(contentLocation.ToUpper());
					return true;
				}
			}

			return false;
		}

        public static void SetDefaultService(IBlogDataService blogDataService)
        {
            defaultService = blogDataService;
        }
	}   

    internal class BlogDataServiceXml : IBlogDataService
    {
		private string contentBaseDirectory;
        private string binariesBaseDirectory;
		private DataManager data;
		private ILoggingDataService loggingService;
        private TrackingTools trackingTools;
		private CommentFile allComments;
        protected string ContentBaseDirectory
        {
            get
            {
				return contentBaseDirectory;
			}
		}


		/// <summary>
		/// The BlogDataServiceXml constructor is entrypoint for the dasBlog Runtime.
		/// </summary>
		/// <param name="contentLocation">The path of the content directory</param>
		/// <param name="loggingService">The <see cref="ILoggingDataService"/></param>
		internal BlogDataServiceXml(string contentLocation, string binariesLocation, ILoggingDataService loggingService) {
			contentBaseDirectory = contentLocation;
            binariesBaseDirectory = binariesLocation;
			this.loggingService = loggingService;
            trackingTools = new TrackingTools(this, loggingService);

            if (!Directory.Exists(contentBaseDirectory))
            {
				throw new ArgumentException(
					String.Format("Invalid directory {0}", contentBaseDirectory),
					"contentLocation");
			}
			data = new DataManager();
			data.Resolver = new ResolveFileCallback(this.GetAbsolutePath);
			allComments = new CommentFile( contentBaseDirectory );

			//OmarS: now we want to initialize the EntryIdCache so this doesn't happen elsewhere later on
			this.GetEntryIdCache();
		}

        
        protected string GetAbsolutePath(string file)
        {
			return Path.Combine(contentBaseDirectory,file);
		}

		private EntryIdCache _onlyEntryIdCacheOfOnlyPublicEntries;
		private object _onlyEntryIdCacheOfOnlyPublicEntriesLock = new object();
        public EntryIdCache GetEntryIdCache()
        {
            lock (_onlyEntryIdCacheOfOnlyPublicEntriesLock)
            {
				//			EntryIdCache ecache = new EntryIdCache();
				//			ecache.Ensure(data);
				//			return ecache;
				EntryIdCache ecache = new EntryIdCache();
				ecache.Ensure(data);
				// Overly complicated:  
				// We save (cache) the EntryIdCache and use the saved version
				// if not the admin and the ChangeNumber hasn't changed.
                if (!Thread.CurrentPrincipal.IsInRole("admin"))
                {
					if(_onlyEntryIdCacheOfOnlyPublicEntries == null ||
                        (_onlyEntryIdCacheOfOnlyPublicEntries.ChangeNumber != ecache.ChangeNumber))
                    {
						EntryIdCacheEntryCollection entryCacheCollection = new EntryIdCacheEntryCollection();
                        foreach (EntryIdCacheEntry entryIdCacheEntry in ecache.Entries)
                        {
                            if (entryIdCacheEntry.IsPublic)
                            {
								entryCacheCollection.Add(entryIdCacheEntry);
							}
						}
						ecache.Entries = entryCacheCollection;
						_onlyEntryIdCacheOfOnlyPublicEntries = ecache;
					}
                    else
                    {
						// Since change numbers are the same return the saved (cached) version.
						ecache = _onlyEntryIdCacheOfOnlyPublicEntries;
					}
				}
				return ecache;
			}
		}

        protected DateTime GetDateForEntry(string entryId)
        {
            if (entryId.Length == 0)
            {
				HttpContext.Current.Trace.Write("entryId is empty!");
			}
			EntryIdCache ecache = GetEntryIdCache();

			DateTime foundDate = ecache.GetDateFromEntryId(entryId);	
            if (foundDate == DateTime.MinValue)
            {
				foundDate = ecache.GetDateFromCompressedTitle(entryId);
			}

			//SDH: Folks find this warning more irritating and confusing than helpful.
			/* 
			 if ((foundDate == DateTime.MinValue))
			{
				StackTrace st = new StackTrace();
				try
				{
					// if we are running from the runtime, but not asp.net this will fail
					loggingService.AddEvent(new EventDataItem(EventCodes.Error,"GetDateForEntry: Can't find \"" + entryId + "\" " + st.ToString() + " " + HttpContext.Current.Request.RawUrl,String.Empty));
				}
				catch
				{
					loggingService.AddEvent(new EventDataItem(EventCodes.Error,"GetDateForEntry: Can't find \"" + entryId + "\" " + st.ToString(),String.Empty));					
				}
			}
			*/
			return foundDate;
		}

        DayEntry InternalGetDayEntry(DateTime date)
        {
			DayEntry result = null;

			//System.Diagnostics.Debug.Write("Loading DayEntry for " + date.ToShortDateString());

			if (data.Days.ContainsKey(date)) 
			{
				DayEntry day = data.Days[date];
				day.Load(data);
				result = day;
			}
			
			/*
			foreach (DayEntry day in data.Days)
			{
				if (day.DateUtc == date)
				{
					day.Load(data);
					result = day;
					break;
				}
			}
			*/

            if (result == null)
            {
				result = new DayEntry();
				result.Initialize();
				result.DateUtc = date;
				result.Save(data);
				data.IncrementEntryChange();
			}

			return result;
		}

        DayEntry IBlogDataService.GetDayEntry(DateTime date)
        {
			return InternalGetDayEntry( date );
		}

		/// <summary>
		/// Returns loaded DayEntries that correspond to the criteria
		/// </summary>
		/// <param name="maxDays"></param>
		/// <param name="dayEntryCriteria"></param>
		/// <returns></returns>
		protected DayEntryCollection InternalGetDayEntries(int maxDays, 
            DayEntryCollection.CriteriaHandler dayEntryCriteria)
        {
			DayEntryCollection days = new DayEntryCollection();
			DayEntryCollection dataDays = data.Days;

            for (int j = 0; j < dataDays.Count; j++)
            {
                if (dayEntryCriteria != null)
                {
					bool include = true;
                    foreach (DayEntryCollection.CriteriaHandler criteria in dayEntryCriteria.GetInvocationList())
                    {
                        if (!criteria(dataDays[j]))
                        {
							include = false;
						}
					}
                    if (include)
                    {
						days.Add(dataDays[j]);
					}
				}
                else
                {
					days.Add(dataDays[j]);
				}
				if ( days.Count >= maxDays )
					break;
			}

			return days;
		}


		/// <summary>
		/// Load the DayEntries that match the criteria of the includeDayEntry delegate
		/// </summary>
		/// <param name="dayEntryCriteria">A delegate that returns true for each DayEntry that should be included in the DayEntryCollection returned</param>
		/// <returns></returns>
		protected DayEntryCollection InternalGetDayEntries(
            DayEntryCollection.CriteriaHandler dayEntryCriteria)
        {
			return InternalGetDayEntries(int.MaxValue, dayEntryCriteria);
		}

		/// <summary>
		/// Gets a collection of <see cref="newtelligence.DasBlog.Runtime.DayEntry"/> structures for dates starting at 
		/// the <paramref name="startDate"/> backwards for at most 
		/// <paramref name="maxDays"/>.
		/// </summary>
		/// <param name="startDate">Date at which to start collecting DayEntry structures</param>
		/// <param name="maxDays">Maximum number of days to return. This number relates to
		/// days actually found and not to calendar days.</param>
		/// <returns>A DayEntryCollection containing the collected results.</returns>
        protected DayEntryCollection InternalGetDayEntries(DateTime startDate, TimeZone tz, int maxDays)
        {
			// we look one day ahead into "UTC" future in order to grab 
			// the timezones ahead of UTC.
			return InternalGetDayEntries(maxDays, 
				DayEntryCollection.CriteriaHandlerFactory.OccursBefore(startDate.Date.AddDays(1)));

		}

		/// <summary>
		/// Gets the DayExtra structure for a given date.
		/// </summary>
		/// <param name="date">Date for which the structure shall be returned.</param>
		/// <returns>A day extra structure for the given day.</returns>
        protected DayExtra InternalGetDayExtra(DateTime date)
        {
			DayExtra extra = data.GetDayExtra(date);
			// extra.Load( data ); // we don't need to call this twice
			return extra;
		}
        
        DayExtra IBlogDataService.GetDayExtra(DateTime date)
        {
			return InternalGetDayExtra( date );
		}
        
        
        protected Entry InternalGetEntry(string entryId)
        {
			Entry entryResult = null;
			DayEntry day;
			
			// The entry lookup hashtables use the UrlEncoded version of the title
			entryId = HttpUtility.UrlEncode(entryId);

			DateTime foundDate = GetDateForEntry( entryId );
            if (foundDate == DateTime.MinValue)
            {
				entryResult = null;
			}
            else
            {
				day = InternalGetDayEntry( foundDate );

                if (day.Entries.ContainsKey(entryId))
                {
					entryResult = day.Entries[entryId];
				}

				// entryId not found, so find by title
                if (entryResult == null)
                {
					entryResult = day.GetEntryByTitle(entryId);
				}
			}

			// Don't return entries where IsPublic is false
			// unless the user is in the "admin" role.
			if( (entryResult != null) 
				&& (!entryResult.IsPublic)
                && !Thread.CurrentPrincipal.IsInRole("admin"))
            {
				entryResult = null;
			}

			return entryResult;
		}

		/// <summary>
		/// Returns the Entry for a given entryId. 
		/// </summary>
		/// <param name="entryId"></param>
		/// <returns></returns>
        Entry IBlogDataService.GetEntry(string entryId)
        {
			return InternalGetEntry(entryId);
		}

		//		/// <summary>
		//		/// Returns the Entry for a given entryId. 
		//		/// </summary>
		//		/// <param name="entryId"></param>
		//		/// <returns></returns>
		//		string IBlogDataService.GetEntryTitle( string entryId )
		//		{
		//			EntryIdCache ecache = GetEntryIdCache();
		//			string title = ecache.GetTitleFromEntryId(entryId);
		//			return title;
		//		}

		/// <summary>
		/// Returns a copy of the Entry for a given entryId. You must Save the Entry for changes to be
		/// reflected in the Runtime.
		/// </summary>
		/// <param name="entryId"></param>
		/// <returns></returns>
        Entry IBlogDataService.GetEntryForEdit(string entryId)
        {
			return InternalGetEntry(entryId).Clone();
		}


        public EntryCollection /*IBlogDataService*/ GetAllEntries( bool infoOnly )
        {
            if (infoOnly)
            {
                EntryCollection entries = new EntryCollection();
                EntryIdCache cache = GetEntryIdCache();
                foreach (EntryIdCacheEntry cacheEntry in cache.Entries)
                {
                    Entry entry = new Entry();
                    entry.EntryId = cacheEntry.EntryId;
                    entry.CreatedUtc = cacheEntry.DateUtc;
                    entry.Title = cacheEntry.Title;
                    entry.IsPublic = cacheEntry.IsPublic;
                    entries.Add(entry);                        
                }
                return entries;
            }
            else
            {
                return GetEntries(null, null, int.MaxValue, int.MaxValue);
            }
        }

		/// <summary>
		/// Returns an EntryCollection whose entries all fit the criteria
		/// specified by include.
		/// </summary>
		/// <param name="dayEntryCriteria">A delegate that specifies which days should be included.</param>
		/// <param name="entryCriteria">A delegate that specifies which entries should be included.</param>
		/// <param name="maxDays">The maximum number of days to include.</param>
		/// <param name="maxEntries">The maximum number of entries to return.</param>
		/// <returns></returns>
		public EntryCollection /*IBlogDataService*/ GetEntries(
			DayEntryCollection.CriteriaHandler dayEntryCriteria, 
            Predicate<Entry> entryCriteria,
            int maxDays, int maxEntries)
        {
			EntryCollection entries = new EntryCollection();
			DayEntryCollection days;
			int entryCount = 0;

			days = InternalGetDayEntries( maxDays, dayEntryCriteria);

            foreach (DayEntry day in days)
            {
				day.Load(data);
				
                foreach (Entry entry in day.GetEntries(entryCriteria))
                {
                    if (entryCount < maxEntries)
                    {
						entries.Add( entry );
						entryCount++;
					}
                    else
                    {
						break;
					}
				}
                if (entryCount >= maxEntries)
                {
					break;     
				}
			}
			return entries;
		}

		/// <summary>
		/// Gets a collection of at most <paramref name="maxEntries"/> <see cref="newtelligence.DasBlog.Runtime.Entry"/> 
		/// structures for dates starting at the <paramref name="startDateUtc"/> 
		/// backwards for at most <paramref name="maxDays"/>. 
		/// The collection can optionally be  
		/// filtered by a categoryName.
		/// </summary>
		/// <param name="startDateUtc">UTC normalized date at which to start collecting DayEntry structures. See remarks.</param>
		/// <param name="maxDays">Maximum number of days to return. This number relates to
		/// days actually found and not to calendar days.</param>
		/// <param name="maxEntries"></param>
		/// <param name="categoryName">Optional category filter. May be empty or null.</param>
		/// <returns></returns>
		/// <remarks>
		///     The start date is expressed as a date relative to the UTC timezone and is normalized to
		///     UTC 0000 hrs. The TimeZone passed to this method serves to offset UTC into display time.
		///     
		/// </remarks>
		// TODO:  Consider refactoring to use InternalGetDayEntries that takes delegates.
		EntryCollection IBlogDataService.GetEntriesForDay(
            DateTime startDateUtc, TimeZone tz, string acceptLanguages, int maxDays, int maxEntries, string categoryName)
        {
			EntryCollection entries;
            Predicate<Entry> entryCriteria = null;
                     


			// the entry is only eligible if its timezone time is within start date or earlier 
			//entryCriteria += EntryCollection.CriteriaHandlerFactory.OccursBetween(
			//	tz, startDateUtc.Date, startDateUtc.AddDays(1).AddSeconds(-1) );
			if ( categoryName != null && 
                categoryName.Length > 0)
            {
                entryCriteria += EntryCollectionFilter.DefaultFilters.IsInCategory(categoryName);
			}	

            if (acceptLanguages != null && acceptLanguages.Length > 0)
            {
                entryCriteria += EntryCollectionFilter.DefaultFilters.IsInAcceptedLanguagesOrMultiLingual(acceptLanguages);
			}

			// HACK AG AddDays(1) removed to prevent showing entries after the selected date.
			entries = GetEntries(
				DayEntryCollection.CriteriaHandlerFactory.OccursBefore(startDateUtc.Date),
				entryCriteria,
				maxDays, maxEntries);

			return entries;
		}


		EntryCollection IBlogDataService.GetEntriesForMonth( 
            DateTime month, TimeZone timeZone, string acceptLanguages)
        {
			EntryCollection entries;
           Predicate<Entry> entryCriteria = null;
			int daysInMonth;

			// The number of days in the month is equivalent to the last day of the month.
			daysInMonth = (new DateTime(month.Year, month.Month, 1, 0, 0, 0).AddMonths(1).AddSeconds(-1)).Day;

			// the entry is only eligible if its timezone time is within start date or earlier 
            entryCriteria += EntryCollectionFilter.DefaultFilters.OccursInMonth(
				timeZone, month);

            if (acceptLanguages != null && acceptLanguages.Length > 0)
            {
                entryCriteria += EntryCollectionFilter.DefaultFilters.IsInAcceptedLanguagesOrMultiLingual(acceptLanguages);
			}
					
			// TODO:  In theory it should be unnecessary to specify the maxDays because there cannot be more than one
			// DayEntry per day but it existed in previous code so .  Verify and then remove.
			entries = GetEntries(DayEntryCollection.CriteriaHandlerFactory.OccursInMonth(timeZone, month),
				entryCriteria, daysInMonth, int.MaxValue);

			return entries;
		}

		// TODO:  Consider refactoring to use InternalGetDayEntries that takes delegates.  It is slightly more
		// complicated because this method uses CategoryCache().
        EntryCollection IBlogDataService.GetEntriesForCategory(string categoryName, string acceptLanguages)
        {
			CategoryCache cache = new CategoryCache();
			cache.Ensure(data);

			EntryCollection entryList = new EntryCollection();
			Entry entry;

			CategoryCacheEntry catEntry = cache.Entries[categoryName];
            if (catEntry != null)
            {
                foreach (CategoryCacheEntryDetail detail in catEntry.EntryDetails)
                {
					DayEntry day = data.Days[detail.DayDateUtc];
                    if (day != null)
                    {
                       Predicate<Entry> entryCriteria = null;

                        if (acceptLanguages != null && acceptLanguages.Length > 0)
                        {
                            entryCriteria += EntryCollectionFilter.DefaultFilters.IsInAcceptedLanguagesOrMultiLingual(acceptLanguages);
						}


						day.Load(data);
						entry = day.GetEntries(entryCriteria)[detail.EntryId];
                        if (entry != null)
                        {
							entryList.Add(entry);
						}
					}
				}
			}
			entryList.Sort(new EntrySorter());
			return entryList;
		}

        EntryCollection IBlogDataService.GetEntriesForUser(string user)
        {
            Predicate<Entry> entryCriteria = null;
			EntryCollection entries = new EntryCollection();
            entryCriteria += EntryCollectionFilter.DefaultFilters.IsFromUser(user);

			DayEntryCollection dayEntries = InternalGetDayEntries(int.MaxValue, null);

            foreach (DayEntry dayEntry in dayEntries)
            {
				foreach(Entry entry in dayEntry.GetEntries(entryCriteria))
					entries.Add(entry);
			}

			return entries;
		}

        DateTime[] IBlogDataService.GetDaysWithEntries(TimeZone tz)
        {
			EntryIdCache idCache = GetEntryIdCache();
			ArrayList dayList = new ArrayList();

            foreach (EntryIdCacheEntry entry in idCache.Entries)
            {
				DateTime tzTime = tz.ToLocalTime( entry.DateUtc ).Date;
                if (!dayList.Contains(tzTime))
                {
					dayList.Add( tzTime );
				}
			}
			return dayList.ToArray( typeof(DateTime) ) as DateTime[];
		}

        void IBlogDataService.DeleteEntry(string entryId, CrosspostSiteCollection crosspostSites)
        {
			DateTime foundDate = GetDateForEntry( entryId );
			if ( foundDate == DateTime.MinValue)
				return;            

			DayEntry day = InternalGetDayEntry( foundDate );
			Entry currentEntry = day.Entries[entryId];

            if (currentEntry != null)
            {
                if (crosspostSites != null)
                {
                    foreach (Crosspost cp in currentEntry.Crossposts)
                    {
                        foreach (CrosspostSite site in crosspostSites)
                        {
                            if (site.ProfileName == cp.ProfileName)
                            {
                                try
                                {
									
									UriBuilder uriBuilder = new UriBuilder("http",site.HostName,site.Port,site.Endpoint);
                                    BloggerAPIClientProxy proxy = new BloggerAPIClientProxy(uriBuilder.Uri);

									proxy.blogger_deletePost("",cp.TargetEntryId,site.Username,site.Password,true);

                                    if (loggingService != null)
                                    {
										loggingService.AddEvent(
											new EventDataItem(EventCodes.CrosspostDeleted, currentEntry.Title, site.ProfileName)); 
									}
								}
                                catch (Exception e)
                                {
									ErrorTrace.Trace(TraceLevel.Error,e);
                                    if (loggingService != null)
                                    {
										loggingService.AddEvent(
											new EventDataItem(EventCodes.Error,
											e.ToString().Replace("\n","<br>"),
											String.Format("Deleting cross-post entry {0} from {1}",cp.TargetEntryId,cp.ProfileName)));
									}
								}
								break;
							}
                     
						}
					}
				}

				day.Entries.Remove( currentEntry );
			}
			day.Save( data );
		}

        EntrySaveState IBlogDataService.SaveEntry(Entry entry, params object[] trackingInfos)
        {
			bool found=false;
            
			if ( entry.EntryId == null || entry.EntryId.Length == 0)
				return EntrySaveState.Failed;

			DayEntry day = InternalGetDayEntry( entry.CreatedUtc.Date );
			Entry currentEntry = day.Entries[entry.EntryId];

			// OmarS: now that all the entries are returned from a cache, and not desrialized
			// for each request, users who call GetEntry() will get the current entry in the runtime.
			// That entry can be modified freely, and changes will not be commited till day.Save()
			// is called. However, since they have made changes, and passed in that entry, currentEntry
			// and entry are the same objects (reference the same object) and now the changes are in the day
			// but they haven't been saved. This can cause weird problems, and the runtime may have data
			// that is not commited, and it will be lost if day.Save() is never called.
            if (entry == null)
            {
				throw new ArgumentNullException("Entry is null");
			}

            if (entry.Equals(currentEntry))
            {
				throw new ArgumentException("You have modified an existing entry and are passing that in. You need to call GetEntryForEdit to get a copy of the entry before modifying it");
			}

			//There's a possibility that they've changed the CreatedLocalTime of the entry
			// which means it CURRENTLY lives in one DayEntry file but will soon live in another.
			// We need to find the old version, if it exists, and if the CreatedLocalTime is different
			// than the one being saved now, blow it away.
			Entry originalEntry = this.InternalGetEntry(entry.EntryId);
			DayExtra originalExtra = null;
			
			//Get the comments and trackings for the original entry
			CommentCollection originalComments = null;
			TrackingCollection originalTrackings = null;
			
			//If we found the original and they DID change the CreatedLocalTime
            if (currentEntry == null && originalEntry != null && originalEntry.CreatedLocalTime != entry.CreatedLocalTime)
            {
				//get the comments and trackings
				originalExtra = data.GetDayExtra( originalEntry.CreatedUtc.Date );
                if (originalExtra != null)
                {
					originalComments = originalExtra.GetCommentsFor(originalEntry.EntryId,data);				
					originalTrackings = originalExtra.GetTrackingsFor(originalEntry.EntryId,data);

					//O^n slow...
                    foreach (Comment c in originalComments)
                    {
						originalExtra.Comments.Remove(c);
					}

					//O^n slow...be nice if they were hashed
                    foreach (Tracking t in originalTrackings)
                    {
						originalExtra.Trackings.Remove(t);
					}
					originalExtra.Save(data);
				}


				//Get that original's day and delete the entry
				DayEntry originalDay = InternalGetDayEntry( originalEntry.CreatedUtc.Date );
				originalDay.Entries.Remove(originalEntry);
				originalDay.Save(data);
			}


			// we need to check to see if the two objects are equal so that we avoid trasing
			// data like Crossposts.Clear() which will remove the crosspostInfo from both entries
            if (currentEntry != null && !currentEntry.Equals(entry))
            {
				// we will only change the mod date if there has been a change to a few things
                if (currentEntry.CompareTo(entry) == 1)
                {
					currentEntry.ModifiedUtc = DateTime.Now.ToUniversalTime();
				}

				currentEntry.Categories = entry.Categories;
				currentEntry.Syndicated = entry.Syndicated;
				currentEntry.Content = entry.Content;
				currentEntry.CreatedUtc = entry.CreatedUtc;
				currentEntry.Description = entry.Description;
				currentEntry.anyAttributes = entry.anyAttributes;
				currentEntry.anyElements = entry.anyElements;
				
				currentEntry.Author = entry.Author;
				currentEntry.IsPublic = entry.IsPublic;
				currentEntry.Language = entry.Language;
				currentEntry.AllowComments = entry.AllowComments;
				currentEntry.Link = entry.Link;
				currentEntry.ShowOnFrontPage = entry.ShowOnFrontPage;
				currentEntry.Title = entry.Title;
				
				currentEntry.Crossposts.Clear();
				currentEntry.Crossposts.AddRange(entry.Crossposts);
				currentEntry.Attachments.Clear();
				currentEntry.Attachments.AddRange(entry.Attachments);

				day.Save(data);
				data.lastEntryUpdate = currentEntry.ModifiedUtc;
				data.IncrementEntryChange();
				found = true;
			}
            else
            {
				day.Entries.Add(entry);
				day.Save(data);
				data.lastEntryUpdate = entry.CreatedUtc;
				data.IncrementEntryChange();
				found = false;
			}

			//Now, move the comments and trackings to the new Date
            if (originalEntry != null && originalComments != null && originalExtra != null)
            {
				DayExtra newExtra = data.GetDayExtra(entry.CreatedUtc.Date);
				newExtra.Comments.AddRange(originalComments);
				newExtra.Trackings.AddRange(originalTrackings);
				newExtra.Save(data);
			}
			

            if (trackingInfos != null)
            {
                trackingTools.RunActions(trackingInfos,entry);
			}
			return found?EntrySaveState.Updated:EntrySaveState.Added;
		}
        
        CategoryCacheEntryCollection IBlogDataService.GetCategories()
        {
			CategoryCacheEntryCollection result;
			CategoryCache cache = new CategoryCache();
			cache.Ensure(data);
            if (Thread.CurrentPrincipal.IsInRole("admin"))
            {
				result = cache.Entries;
			}
            else
            {
				result = new CategoryCacheEntryCollection();
                foreach (CategoryCacheEntry category in cache.Entries)
                {
                    if (category.IsPublic)
                    {
						result.Add(category);
					}
				}
			}
			return result;
		}
        
        private void InternalAddTracking(Tracking tracking)
        {
			bool trackFound = false;
            
			Entry entry = InternalGetEntry( tracking.TargetEntryId );
            
            if (entry == null)
            {
				StackTrace st = new StackTrace();
				string logtext = String.Format("InternalAddTracking: Entry not found: {0}, {1}, {2} {3}",
					tracking.TrackingType, tracking.TargetTitle, tracking.TargetEntryId, st.ToString());
				this.loggingService.AddEvent(
					new EventDataItem(EventCodes.Error, logtext, ""));
				return;
			}

			DayExtra extra = InternalGetDayExtra( entry.CreatedUtc );

            if (extra == null)
            {
				StackTrace st = new StackTrace();
				string logtext = String.Format("InternalAddTracking: DayExtra not found: {0}, {1}, {2}, {3} {4}",
					tracking.TrackingType, tracking.TargetTitle, tracking.TargetEntryId, entry.CreatedUtc, st.ToString());
				this.loggingService.AddEvent(
					new EventDataItem(EventCodes.Error, logtext, ""));
				return;
			}

            foreach (Tracking trk in extra.Trackings)
            {
				if (trk.PermaLink == tracking.PermaLink && 
					String.Compare(trk.TargetEntryId, tracking.TargetEntryId, true) == 0 &&
                    trk.TrackingType == tracking.TrackingType)
                {
					trackFound = true;
					break;
				}
			}

            if (!trackFound)
            {
				tracking.TargetTitle = entry.Title;
				extra.Trackings.Add(tracking);
				extra.Save(data);
				data.IncrementExtraChange();
			}
		}

        void IBlogDataService.RunActions(object[] actions)
        {
            trackingTools.RunActions(actions,null);
		}
	
        void IBlogDataService.AddTracking(Tracking tracking, params object[] actions)
        {
			((IBlogDataService)this).RunActions(actions);

            ThreadPool.QueueUserWorkItem(delegate(object t){
                InternalAddTracking(t as Tracking);
            }, tracking);            
		}

        void IBlogDataService.DeleteTracking(string entryId, string trackingPermalink, TrackingType trackingType)
        {
			DateTime date = GetDateForEntry( entryId );
            if (date != DateTime.MinValue)
            {
				DayExtra extra = data.GetDayExtra( date );

                for (int i = 0; i < extra.Trackings.Count; i++)
                {
					Tracking tracking = extra.Trackings[i];
                    if (tracking.PermaLink != null && tracking.PermaLink.Length > 0)
                    {
						// Trimming PermaLink to fix bug 1278194: PermaLink may be stored with '\r' appended.
                        if (String.Compare(tracking.PermaLink.Trim(), trackingPermalink, true) == 0 && trackingType == tracking.TrackingType)
                        {
							extra.Trackings.Remove(tracking);
							extra.Save(data);
							break;
						}
					}
				}
			}
		}

        TrackingCollection IBlogDataService.GetTrackingsFor(string entryId)
        {
			TrackingCollection trackingsForEntry = new TrackingCollection();
			DateTime date = GetDateForEntry( entryId );
			if ( date == DateTime.MinValue )
				return trackingsForEntry;

			DayExtra extra = data.GetDayExtra( date );
            foreach (Tracking trk in extra.Trackings)
            {
                if (trk.TargetEntryId.ToUpper() == entryId.ToUpper())
                {
					trackingsForEntry.Add( trk );
				}
			}
			return trackingsForEntry;
		}

        void IBlogDataService.AddComment(Comment comment, params object[] actions)
        {

			DateTime date = GetDateForEntry( comment.TargetEntryId );
			if ( date == DateTime.MinValue )
				return;

			//Don't allow anyone to add a comment to a closed entry...
			Entry e = this.InternalGetEntry( comment.TargetEntryId );
			if (e == null || e.AllowComments == false) 
			{
				return;
			}

			((IBlogDataService)this).RunActions(actions);
			data.lastCommentUpdate = comment.CreatedUtc;
			DayExtra extra = data.GetDayExtra( date );
			extra.Comments.Add(comment);
			extra.Save(data);
			data.IncrementExtraChange();
			// update the all comments file
			allComments.AddComment( comment );
		}

        Comment IBlogDataService.GetCommentById(string entryId, string commentId)
        {
			DateTime date = GetDateForEntry( entryId );
			if ( date == DateTime.MinValue )
				return null;

			DayExtra extra = data.GetDayExtra( date );

			return extra.Comments[commentId];
		}

        void IBlogDataService.DeleteComment(string entryid, string commentid)
        {
			DateTime date = GetDateForEntry( entryid );
			if ( date == DateTime.MinValue )
				return;

			DayExtra extra = data.GetDayExtra( date );

			Comment c = extra.Comments[commentid];
            if (c != null)
            {
				extra.Comments.Remove( c );
				extra.Save(data);
				data.IncrementExtraChange();

				// update the all comments file
				allComments.DeleteComment( commentid );
			}
		}

        void IBlogDataService.ApproveComment(string entryId, string commentId)
        {
			DateTime date = GetDateForEntry( entryId );
			if ( date == DateTime.MinValue )
				return;

			DayExtra extra = data.GetDayExtra( date );

			Comment c = extra.Comments[commentId];
            if (c != null)
            {
				c.IsPublic = true;
				c.SpamState = SpamState.NotSpam;
				extra.Save(data);
				data.IncrementExtraChange();

				// update the all comments file
				allComments.UpdateComment( c );
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="entryId"></param>
		/// <remarks>Only gets the public comments.</remarks>
		[Obsolete( "Use the overload to indicate you want allComments, or just the public.")]
        CommentCollection IBlogDataService.GetCommentsFor(string entryId)
        {
			return InternalGetCommentsFor(entryId, false);
		}

		/// <summary>
		/// Gets the public comments for the entry.
		/// </summary>
		/// <param name="entryId">The entry id.</param>
		/// <returns>
		/// A collection of public comments for the entry.
		/// </returns>
        CommentCollection IBlogDataService.GetPublicCommentsFor(string entryId)
        {
			return InternalGetCommentsFor( entryId, false );
		}

		/// <summary>
		/// </summary>
		/// <param name="entryId"></param>
		/// <param name="allComments">Indicates wheter to get all comments, or only the public comments.</param>
		/// <returns></returns>
        CommentCollection IBlogDataService.GetCommentsFor(string entryId, bool allComments)
        {
			return InternalGetCommentsFor(entryId, allComments);
		}

        CommentCollection InternalGetCommentsFor(string entryId, bool allComments)
        {
			
			CommentCollection commentsForEntry = new CommentCollection();
			DateTime date = GetDateForEntry( entryId );
			if ( date == DateTime.MinValue )
				return commentsForEntry;

			DayExtra extra = data.GetDayExtra( date );
            foreach (Comment cm in extra.Comments)
            {

				// check if the comment is for this entry, and is public or allComments are requested
                if (String.Compare(cm.TargetEntryId, entryId, true, CultureInfo.InvariantCulture) == 0 && (cm.IsPublic || allComments))
                {
					commentsForEntry.Add( cm );
				}
			}
			return commentsForEntry;
		}
			
		/// <summary>
		/// Gets all comments for this blog.
		/// </summary>
		/// <returns></returns>
		/// <remarks>This method will be extremely slow on a blog with a lot of content.</remarks>
        CommentCollection IBlogDataService.GetAllComments()
        {

			CommentCollection _com = null;
			
			// get the comments
			_com = allComments.LoadComments();

//			if (_com == null) {
//				// recreate the comments file.
//				_com = new CommentCollection();
//				_com.Rebuild();
//				// save the comments
//				allComments.SaveComments( _com );
//
//			}
			return _com;
		}

		/// <summary>
		/// The DateTime of the last modified or created post.
		/// </summary>
		/// <returns>DateTime of the last entry modification in UTC </returns>
        DateTime IBlogDataService.GetLastEntryUpdate()
        {
			return data.lastEntryUpdate;
		}

		/// <summary>
		/// This DateTime of the most recent comment entry
		/// </summary>
		/// <returns>DateTime of the last comment entry in UTC</returns>
        DateTime IBlogDataService.GetLastCommentUpdate()
        {

            if (data.lastCommentUpdate == DateTime.MinValue)
            {

				data.lastCommentUpdate=allComments.GetLastCommentUpdate();
			}

			return data.lastCommentUpdate;
		}

        string IBlogDataService.AddFile(string entryId, string fileName, string contentType, long contentLength, Stream contentData)
        {
            long numBytes = 0;
            bool alreadyUploaded = false;
            string targetPath = String.Empty;
            if (entryId != null && entryId.Length > 0)
            {
                targetPath = Path.Combine(binariesBaseDirectory, entryId);
            }
            else
            {
                targetPath = binariesBaseDirectory;
            }

            string baseFileName = Path.GetFileName(fileName);
            string targetFileName = Path.Combine(targetPath, baseFileName);
            int numSuffix = 1;

            while (File.Exists(targetFileName))
            {
                byte[] targetBuffer = new byte[512];
                byte[] sourceBuffer = new byte[512];
                int targetBytesRead, sourceBytesRead;

                if (contentData.CanSeek)
                {
                    using (FileStream targetFile = new FileStream(targetFileName, FileMode.Open))
                    {
                        numBytes = targetFile.Length;
                        targetBytesRead = targetFile.Read(targetBuffer, 0, targetBuffer.Length);
                        sourceBytesRead = contentData.Read(sourceBuffer, 0, targetBytesRead);
                        contentData.Position = 0;
                        if (targetBytesRead == sourceBytesRead &&
                            EqualBuffers(targetBuffer, sourceBuffer))
                        {
                            alreadyUploaded = true;
                        }
                    }
                }

                if (!alreadyUploaded)
                {
                    string ext = Path.GetExtension(baseFileName);
                    string file = Path.GetFileNameWithoutExtension(baseFileName);
                    string newFileName = file + (numSuffix++).ToString();
                    baseFileName = newFileName + ext;
                    targetFileName = Path.Combine(targetPath, baseFileName);
                }
                else
                {
                    break;
                }
            }

            if (!alreadyUploaded)
            {
                // create the entry directory
                try
                {
                    DirectoryInfo entryDirectory = new DirectoryInfo(targetPath);
            if (!entryDirectory.Exists)
                    {
                        entryDirectory.Create();
                    }
                }
                catch (Exception e)
                {
                    ErrorTrace.Trace(TraceLevel.Error, e);
                }

                using (FileStream fileStream = new FileStream(targetFileName, FileMode.Create))
                {
                    byte[] buffer = new byte[65536];
                    int bytesRead;
                    do
                    {
                        bytesRead = contentData.Read(buffer, 0, buffer.Length);
                        fileStream.Write(buffer, 0, bytesRead);
                    } while (bytesRead == buffer.Length);
                    fileStream.Flush();
                    numBytes = fileStream.Length;
                }
            }
            return baseFileName;
        }

        private bool EqualBuffers(byte[] buf1, byte[] buf2)
        {
            if (buf1.Length == buf2.Length)
            {
                for (int l = 0; l < buf1.Length; l++)
                {
                    if (buf1[l] != buf2[l])
                        return false;
                }
                return true;
            }
            return false;
        }

        string[] IBlogDataService.GetFileList(string entryId)
        {
            if (entryId != null && entryId.Length > 0)
            {
                string targetPath = Path.Combine(binariesBaseDirectory, entryId);
                if (Directory.Exists(targetPath))
                {
                    return Directory.GetFiles(targetPath);
                }
            }
            return null;
        }

        void IBlogDataService.DeleteFiles(string entryId)
        {
            if (entryId != null && entryId.Length > 0)
            {
                string targetPath = Path.Combine(binariesBaseDirectory, entryId);
                if (Directory.Exists(targetPath))
                {
                    Directory.Delete(targetPath);
                }
            }            
        }
    }   
}
